#ifndef MUTEX_H
#define MUTEX_H

#include "Thread.h"
#include <assert.h>
#include <pthread.h>
#include <time.h>
#include <errno.h>
#include <stdio.h>



__BEGIN_DECLS
extern void __assert_perror_fail (int errnum,
                                  const char *file,
                                  unsigned int line,
                                  const char *function)
    __THROW __attribute__ ((__noreturn__));
__END_DECLS

#define MCHECK(ret) ({ __typeof__ (ret) errnum = (ret);         \
                       if (__builtin_expect(errnum != 0, 0))    \
                         __assert_perror_fail (errnum, __FILE__, __LINE__, __func__);})


namespace MyNoCopyable
{
    class noncopyable
    {
        public:
            noncopyable(){}
            ~noncopyable(){}

        private:
            noncopyable(const noncopyable &rhs);
            noncopyable operator=(const noncopyable &rhs);
    };
}

class CMutexLock
{
public:
    CMutexLock();
    ~CMutexLock();
    void AssertLocked() const
    {
        assert(IsLockedByThisThread());
    }

private:
    pthread_mutex_t m_mutex = PTHREAD_MUTEX_INITIALIZER;
    pid_t m_holder;
    friend class CConditionVariable;
    friend class CSynchronizeAccess;

    void Lock();
    void UnLock();
    pthread_mutex_t* GetPthreadMutex() /* non-const */
    {
        return &m_mutex;
    }

    void UnassignHolder()
    {
        m_holder = 0;
    }

    void AssignHolder()
    {
        m_holder = CurrentThread::Tid();
    }

    bool IsLockedByThisThread() const
    {
        return m_holder == CurrentThread::Tid();
    }
};

class CConditionVariable : public MyNoCopyable::noncopyable
{
public:
    explicit CConditionVariable(CMutexLock &mutex)
    : m_mutex(mutex)
    {
    }

    ~CConditionVariable()
    {
    }

    void Wait()
    {
        MCHECK(pthread_cond_wait(&m_pcond, m_mutex.GetPthreadMutex()));
    }

    // returns true if time out, false otherwise.
    bool WaitForSeconds(int seconds)
    {
        struct timespec abstime;
        // FIXME: use CLOCK_MONOTONIC or CLOCK_MONOTONIC_RAW to prevent time rewind.
        clock_gettime(CLOCK_REALTIME, &abstime);
        abstime.tv_sec += seconds;
        return ETIMEDOUT == pthread_cond_timedwait(&m_pcond, m_mutex.GetPthreadMutex(), &abstime);
    }

    void Notify()
    {
        MCHECK(pthread_cond_signal(&m_pcond));
    }

    void NotifyAll()
    {
        MCHECK(pthread_cond_broadcast(&m_pcond));
    }

 private:
    CMutexLock& m_mutex;
    pthread_cond_t m_pcond = PTHREAD_COND_INITIALIZER;
};

class CSynchronizeAccess : public MyNoCopyable::noncopyable
{
public:
    explicit CSynchronizeAccess(CMutexLock &lock)
    : m_MutexLock(lock)
    {
        m_MutexLock.Lock();
    }
    
    ~CSynchronizeAccess()
    {
        m_MutexLock.UnLock();
    }

private:
    CMutexLock &m_MutexLock;
};

#define CSynchronizeAccess(x) error "Missing guard object name"

#endif // MUTEX_H
